package com.dozen.commonbase.view.cube;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Shader;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.os.SystemClock;
import android.util.Log;

import com.dozen.commonbase.utils.EmptyCheckUtil;
import com.dozen.commonbase.utils.FileUtil;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
 * @author: Dozen
 * @description:
 * @time: 2021/11/9
 */
public class CubeRenderer implements GLSurfaceView.Renderer {
    private Context mContext;
    private FloatBuffer mCubePositions;
    private FloatBuffer mCubeTextureCoord;
    private float[] mMVPMatrix = new float[16];
    private float[] mViewMatrix = new float[16];
    private float[] mModelMatrix = new float[16];
    private float[] mProjectionMatrix = new float[16];
    private int[] mTextureDataHandle = new int[6];

    private int mMVPMatrixHandle;
    private int mPositionHandle;
    private int mTextureCoordinateHandle;
    private final int POSITION_DATA_SIZE = 3;
    private final int TEXTURE_COORDINATE_DATA_SIZE = 2;

    private CubeBean cubeBean;

    public int fps;
    public FPSCounter fpsCounter;
    public float mAngleX = 0, mAngleY = 0;

    public CubeRenderer(final Context context, CubeBean cubeBean) {
        mContext = context;
        this.cubeBean = cubeBean;
        initCube();
    }

    private void initCube() {
        final float cubePosition[] = {
                -1.0f, -1.0f, 0.0f,  // 0. 左-底-前
                1.0f, -1.0f, 0.0f,   // 1. 右-底-前
                -1.0f, 1.0f, 0.0f,  // 2. 左-顶-前
                1.0f, 1.0f, 0.0f    // 3. 右-顶-前
        };
        final float cubeTextureCoordinate[] = {
                0.0f, 1.0f,  // A. 左-下
                1.0f, 1.0f,  // B. 右-下
                0.0f, 0.0f,  // C. 左-上
                1.0f, 0.0f   // D. 右-上
        };

        mCubePositions = ByteBuffer.allocateDirect(cubePosition.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubePositions.put(cubePosition).position(0);

        mCubeTextureCoord = ByteBuffer.allocateDirect(cubeTextureCoordinate.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubeTextureCoord.put(cubeTextureCoordinate).position(0);

        fpsCounter = new FPSCounter();
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES20.glClearColor(0, 0, 0, 0);
        GLES20.glEnable(GLES20.GL_CULL_FACE);
        GLES20.glEnable(GLES20.GL_DEPTH_TEST);
        GLES20.glEnable(GL10.GL_MULTISAMPLE);
        String vertexShaderCode =
                "uniform mat4 u_MVPMatrix; \n"
                        + "attribute vec4 a_Position; \n"
                        + "attribute vec2 a_TexCoordinate;\n"
                        + "varying vec2 v_TexCoordinate; \n"
                        + "void main(){ \n"
                        + "  v_TexCoordinate = a_TexCoordinate;\n"
                        + "  gl_Position = u_MVPMatrix * a_Position; \n"
                        + "} \n";
        String fragmentShaderCode =
                "precision mediump float; \n"
                        + "uniform sampler2D u_Texture; \n"
                        + "varying vec4 v_Color; \n"
                        + "varying vec2 v_TexCoordinate; \n"
                        + "void main(){ \n"
                        // " gl_FragColor = v_Color * texture2D(u_Texture, v_TexCoordinate); \n"
                        + "  gl_FragColor = texture2D(u_Texture, v_TexCoordinate); \n"
                        + "} \n";
        int mProgramHandle = createAndLinkProgram(vertexShaderCode, fragmentShaderCode);
        GLES20.glUseProgram(mProgramHandle);
        //获得参数1-3
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgramHandle, "u_MVPMatrix");
        mPositionHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_Position");
        mTextureCoordinateHandle = GLES20.glGetAttribLocation(mProgramHandle, "a_TexCoordinate");
        //加载纹理贴图
        mTextureDataHandle[0] = ToolsUtil.loadTexture(mContext, cubeBean, 0);
        mTextureDataHandle[1] = ToolsUtil.loadTexture(mContext, cubeBean, 1);
        mTextureDataHandle[2] = ToolsUtil.loadTexture(mContext, cubeBean, 2);
        mTextureDataHandle[3] = ToolsUtil.loadTexture(mContext, cubeBean, 3);
        mTextureDataHandle[4] = ToolsUtil.loadTexture(mContext, cubeBean, 4);
        mTextureDataHandle[5] = ToolsUtil.loadTexture(mContext, cubeBean, 5);
        //激活纹理
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        //竖屏：width=1080,height=1581
        GLES20.glViewport(0, 0, width, height);
        final float ratio = (float) width / height;
        Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 30);
        Matrix.setLookAtM(mViewMatrix, 0, 5f, 5f, 1f, 0.0f, 0.0f, -5, 0.0f, 1.0f, 0.0f);
    }

    float[][] offset = {
            {0.0f, 0.0f, 1.0f}, //第一个面向前移，将中心点移到正方体中心，滚动就会很自然。
            {1.0f, 0.0f, 1.0f}, //右
            {1.0f, 0.0f, 1.0f}, //后
            {1.0f, 0.0f, 1.0f}, //左
            {0.0f, -1.0f, 1.0f},//下
            {0.0f, 0.0f, 2.0f}, //上
    };

    float[] angle = {0.0f, 90.0f, 90.0f, 90.0f, 90.0f, 180.0f};

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        //矩阵乘法的一个乘数
        Matrix.setIdentityM(mModelMatrix, 0);
        Matrix.translateM(mModelMatrix, 0, 0.0f, 0.0f, -5.0f);
        Matrix.rotateM(mModelMatrix, 0, mAngleX, 1.0f, 0.0f, 0.0f);
        Matrix.rotateM(mModelMatrix, 0, mAngleY, 0.0f, 1.0f, 0.0f);
        //绑定纹理，传递参数，绘制图形
        for (int i = 0; i < 6; i++) {
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle[i]);
            //在上一个变换面的基础上进行旋转变换(注：坐标系也是跟着旋转的)
            if (i < 4)
                Matrix.rotateM(mModelMatrix, 0, angle[i], 0.0f, 1.0f, 0.0f);
            else
                Matrix.rotateM(mModelMatrix, 0, angle[i], 1.0f, 0.0f, 0.0f);
            Matrix.translateM(mModelMatrix, 0, offset[i][0], offset[i][1], offset[i][2]);
            drawCube(mCubePositions, mCubeTextureCoord);
        }
        fps = fpsCounter.getFPS();
    }

    private void drawCube(final FloatBuffer cubePositions, final FloatBuffer cubeTextureCoord) {
        //参数1传递
        Matrix.multiplyMM(mMVPMatrix, 0, mViewMatrix, 0, mModelMatrix, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mMVPMatrix, 0);
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mMVPMatrix, 0);
        //参数2传递
        GLES20.glVertexAttribPointer(mPositionHandle, POSITION_DATA_SIZE,
                GLES20.GL_FLOAT, false, 0, cubePositions);
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        //参数3传递
        GLES20.glVertexAttribPointer(mTextureCoordinateHandle, TEXTURE_COORDINATE_DATA_SIZE,
                GLES20.GL_FLOAT, false, 0, cubeTextureCoord);
        GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);

        //绘制
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
    }

    private int compileShader(final int shaderType, final String shaderSource) {
        int shaderHandle = GLES20.glCreateShader(shaderType);
        if (shaderHandle != 0) {
            GLES20.glShaderSource(shaderHandle, shaderSource);
            GLES20.glCompileShader(shaderHandle);
            final int[] compileStatus = new int[1];
            GLES20.glGetShaderiv(shaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0);
            if (compileStatus[0] == 0) {
                Log.e("ERR", "compiling: " + GLES20.glGetShaderInfoLog(shaderHandle));
                GLES20.glDeleteShader(shaderHandle);
                shaderHandle = 0;
            }
        }
        if (shaderHandle == 0) {
            throw new RuntimeException("Error creating shader.");
        }
        return shaderHandle;
    }

    private int createAndLinkProgram(final String vertexShaderCode, final String fragmentShaderCode) {
        int vertexShaderHandle = compileShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
        int fragmentShaderHandle = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
        int programHandle = GLES20.glCreateProgram();
        if (programHandle != 0) {
            GLES20.glAttachShader(programHandle, vertexShaderHandle);
            GLES20.glAttachShader(programHandle, fragmentShaderHandle);
            GLES20.glLinkProgram(programHandle);
            final int[] linkStatus = new int[1];
            GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0);
            if (linkStatus[0] == 0) {
                Log.e("ERR", "linking: " + GLES20.glGetProgramInfoLog(programHandle));
                GLES20.glDeleteProgram(programHandle);
                programHandle = 0;
            }
        }
        if (programHandle == 0) {
            throw new RuntimeException("Error creating program.");
        }
        return programHandle;
    }

    static class ToolsUtil {
        public static int loadTexture(final Context context, final CubeBean cubeBean, final int id) {
            final int[] textureHandle = new int[1];
            GLES20.glGenTextures(1, textureHandle, 0);
            if (textureHandle[0] != 0) {
                Bitmap bitmap = null;
                if (cubeBean.bgNetLoad) {
                    bitmap = FileUtil.getBitMBitmap(cubeBean.bgHttpUrl[id]);
                }
                if (EmptyCheckUtil.isEmpty(bitmap)) {
                    bitmap = BitmapFactory.decodeResource(context.getResources(), cubeBean.bgID[id]);
                }
                Bitmap bitmapBg = drawBg4Bitmap(cubeBean, id, bitmap);
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
                GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
                GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST);
                GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmapBg, 0);
            }
            if (textureHandle[0] == 0) {
                throw new RuntimeException("failed to load texture");
            }
            return textureHandle[0];
        }

        private static Bitmap drawBg4Bitmap(CubeBean cubeBean, int id, Bitmap orginBitmap) {
            Paint paint = new Paint();

            Paint paintBg = new Paint();
            paintBg.setAntiAlias(true);
            LinearGradient linearGradient = new LinearGradient(0, 0, 400, 400, cubeBean.cubeFaceList.get(id).cubeFaceColor, null, Shader.TileMode.MIRROR);
            paintBg.setShader(linearGradient);

            Paint paintBorder = new Paint();
            paintBorder.setColor(cubeBean.cubeFaceList.get(id).cubeFaceStrokeColor);
            paintBorder.setAntiAlias(true);
            paintBorder.setStyle(Paint.Style.STROKE);//空心
            paintBorder.setStrokeWidth(cubeBean.cubeFaceList.get(id).cubeFaceStrokeWidth);//空心的边框
            paintBorder.setShadowLayer(cubeBean.cubeFaceList.get(id).cubeFaceStrokeRadius,
                    cubeBean.cubeFaceList.get(id).cubeFaceStrokeDx, cubeBean.cubeFaceList.get(id).cubeFaceStrokeDy,
                    cubeBean.cubeFaceList.get(id).cubeFaceStrokeColor);

            int width = cubeBean.cubeWidth;
            int height = cubeBean.cubeHeight;
            int left = width / 2 - orginBitmap.getWidth() / 2;
            int top = height / 2 - orginBitmap.getHeight() / 2;
            if (cubeBean.isCenter) {
                if (width < orginBitmap.getWidth() || height < orginBitmap.getHeight()) {
                    left = 0;
                    top = 0;
                }
            } else {
                width = orginBitmap.getWidth();
                height = orginBitmap.getHeight();
                left = 0;
                top = 0;
            }

            Bitmap bitmap = Bitmap.createBitmap(width, height, orginBitmap.getConfig());
            Canvas canvas = new Canvas(bitmap);
            canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
            canvas.drawRect(0, 0, width, height, paintBg);
            canvas.drawRect(0, 0, width, height, paintBorder);

            canvas.drawBitmap(orginBitmap, left, top, paint);
            return bitmap;
        }
    }

    static class FPSCounter {
        int FPS;
        int lastFPS;
        long tempFPStime;

        public FPSCounter() {
            FPS = 0;
            lastFPS = 0;
            tempFPStime = 0;
        }

        int getFPS() {
            long nowtime = SystemClock.uptimeMillis();
            FPS++;
            if (nowtime - tempFPStime >= 1000) {
                lastFPS = FPS;
                tempFPStime = nowtime;
                FPS = 0;
            }
            return lastFPS;
        }
    }
}
